/**
 * Copyright (2023, ) Institute of Software, Chinese Academy of Sciences
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.ac.iscas.protocols;

import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

import org.hibernate.exception.SQLGrammarException;

import com.fasterxml.jackson.databind.JsonNode;

import cn.ac.iscas.targets.rbd.PostgresTarget;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;

/**
 * 
 * It is used for creating various HttpRequest
 * 
 * @author wuheng@iscas.ac.cn
 * @since 1.0.5
 **/
public class PostgresProtocol {

	
	/**
	 * 日志
	 */
	static final Logger m_logger = Logger.getLogger(PostgresProtocol.class.getName());
	
	/**********************************************************
	 * 
	 * Commons
	 * 
	 **********************************************************/

	/**
	 * 
	 */
	private PostgresProtocol() {
		super();
	}

	
	/**********************************************************
	 * 
	 * Data Mgr
	 * 
	 **********************************************************/
	
	/**
	 * 创建对象的SQL语法
	 */
	public static final String INSERT = "INSERT INTO #NAME# (\"name\", \"source\", \"data\", \"time\") " +
            "VALUES (?, ?, CAST(? AS json), ?)";
	
	/**
	 * 更新对象的SQL语法
	 */
	public static final String UPDATE = "UPDATE #NAME# SET data = CAST(? AS json), time = ? WHERE name = ? AND source = ?";
	
	/**
	 * 删除对象的SQL语法
	 */
	public static final String DELETE = "DELETE FROM #NAME# WHERE name = ? AND source = ?";

	
	/**
	 * @param data    data
	 * @throws Exception Exception
	 */
	public synchronized void saveData(PostgresTarget target, String table , String name, String source, String time, JsonNode json) throws Exception {
		
		EntityManager entityManager = target.getEntityManager();
		EntityTransaction transaction = entityManager.getTransaction();
		
		transaction.begin();
		try {
			Timestamp updatedTime = updatedTime();
			entityManager.createNativeQuery(INSERT.replace(TABLE_NAME, table))
		        .setParameter(1, name)
		        .setParameter(2, source)
		        .setParameter(3, json.toPrettyString())
		        .setParameter(7, updatedTime)
		        .executeUpdate();
			transaction.commit();
		} catch (Exception ex) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
	}

//	/**
//	 * @param data  data
//	 * @throws Exception Exception
//	 */
//	public synchronized void updateData(KubeDataModel data) throws Exception {
//		EntityManager entityManager = pgClient.getEntityManager();
//		EntityTransaction transaction = entityManager.getTransaction();
//		
//		transaction.begin();
//		try {
//			JsonNode value = data.getData();
//			entityManager.createNativeQuery(UPDATE.replace(TABLE_NAME, 
//							table(data.getMeta().getPlural())))
//				.setParameter(1, value.toPrettyString())
//				.setParameter(2, updatedTime())
//		        .setParameter(3, KubeUtil.getName(value))
//		        .setParameter(4, KubeUtil.getNamespace(value))
//		        .setParameter(5, KubeUtil.getGroup(value))
//		        .setParameter(6, System.getenv(Environment.ENV_KUBE_REGION))
//		        .executeUpdate();
//			transaction.commit();
//			m_logger.info("update data sucessfully at " + updatedTime() + ": (" + data.getMeta().getKind() + "," 
//					+ KubeUtil.getNamespace(value) + "," + KubeUtil.getName(value) + ")");
//		} catch (Exception ex) {
//			m_logger.warning("unable to update data: " + ex + ":" + data.getData().toPrettyString());
//			if (transaction.isActive()) {
//				transaction.rollback();
//			}
//		}
//	}
//
//	/**
//	 * @param data data
//	 * @throws Exception Exception
//	 */
//	public synchronized void deleteData(KubeDataModel data) throws Exception {
//		EntityManager entityManager = pgClient.getEntityManager();
//		EntityTransaction transaction = entityManager.getTransaction();
//		
//		transaction.begin();
//		try {
//			JsonNode value = data.getData();
//			entityManager.createNativeQuery(DELETE.replace(TABLE_NAME, 
//							table(data.getMeta().getPlural())))
//		        .setParameter(1, KubeUtil.getName(value))
//		        .setParameter(2, KubeUtil.getNamespace(value))
//		        .setParameter(3, KubeUtil.getGroup(value))
//		        .setParameter(4, System.getenv(Environment.ENV_KUBE_REGION))
//		        .executeUpdate();
//			transaction.commit();
//			m_logger.info("delete data sucessfully at " + updatedTime() + ": (" + data.getMeta().getKind() + "," 
//					+ KubeUtil.getNamespace(value) + "," + KubeUtil.getName(value) + ")");
//		} catch (Exception ex) {
//			m_logger.warning("unable to delete data: " + ex + ":" + data.getData().toPrettyString());
//			if (transaction.isActive()) {
//				transaction.rollback();
//			}
//		}
//	}
	
	/**********************************************************
	 * 
	 * Table Mgr
	 * 
	 **********************************************************/
	
	/**
	 * 创建表的SQL语法
	 */
	public static final String CREATE_TABLE = "CREATE TABLE #NAME# (\r\n"
			+ "    \"name\" character varying(256) NOT NULL,\r\n"
			+ "    \"source\" character varying(64) NOT NULL,\r\n"
			+ "    \"data\" json NOT NULL,\r\n"
			+ "    \"time\" timestamp NOT NULL,\r\n"
			+ "    PRIMARY KEY (\"name\", \"source\", \"time\")\r\n"
			+ ");";
	
	/**
	 * 查询表是否存在SQL语法
	 */
	public static final String QUERY_TABLE = "SELECT EXISTS (\r\n"
			+ "   SELECT 1\r\n"
			+ "   FROM information_schema.tables\r\n"
			+ "   WHERE table_name = '#NAME#'\r\n"
			+ ");";
	
	/**
	 * 删除表的SQL语法
	 */
	public static final String DROP_TABLE = "DROP TABLE #NAME#; ";

	/**
	 * SQL中关键字Name
	 */
	public static final String TABLE_NAME = "#NAME#";
	
	/**
	 * 删除对象的SQL语法
	 */
	public static final String DELETE_DATA = "DELETE FROM #NAME#";

	
	/**
	 * 已经创建的表
	 */
	public static final Set<String> createdTables = new HashSet<>();
	

	public synchronized void createTableIfNeed(PostgresTarget target, String table) throws Exception {
		if (!createdTables.contains(table)) {
			try {
				createTable(target, table);
			} catch (SQLGrammarException ex) {
				m_logger.info("table '" + table + "' has created.");
				EntityTransaction transaction = target.getEntityManager().getTransaction();
				if (transaction.isActive()) {
					transaction.rollback();
				}
				deleteDataIfExist(target, table);
			} 
			
			createdTables.add(table);
		}
	}

	
	/**
	 * @param table     表名，对应plural
	 */
	void createTable(PostgresTarget target, String table) {
		if (target.execWithoutResult(CREATE_TABLE.replace(TABLE_NAME, table))) {
			m_logger.info("create table '" + table + "' sucessfully.");
			return;
		}
		throw new SQLGrammarException("table '" + table + "' has been created.", new SQLException());
	}

	/**
	 * @param table      表名，对应plural
	 */
	void deleteTableIfExist(PostgresTarget target, String table) {
		Object result = target.execWithSingleResult(QUERY_TABLE.replace(TABLE_NAME, table));
		if ((boolean) result) {
			target.execWithoutResult(DROP_TABLE.replace(TABLE_NAME, table));
		}
	}

	void deleteDataIfExist(PostgresTarget target, String table) {
		EntityManager entityManager = target.getEntityManager();
		EntityTransaction transaction = entityManager.getTransaction();
		
		transaction.begin();
		try {
			entityManager.createNativeQuery(DELETE_DATA.replace(TABLE_NAME, table))
		        .executeUpdate();
			transaction.commit();
			m_logger.info("delete all data in table '" + table);
		} catch (Exception ex) {
			m_logger.warning("unable to delete data in table '" + table + "' because of" + ex);
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
	}
	
	
	/**
	 * @return      获得kubernetes的更新时间
	 */
	public static Timestamp updatedTime() {
        return Timestamp.valueOf(java.time.LocalDateTime.now());
	}
}
